<?php

/**
 * Albireo Kernel
 *
 * @copyright  Copyright (c) 2010 Albireo Solutions
 * @package    Kernel
 */
namespace Celebrio\AccessControl;

use \Nette\Object;
use \Nette\Environment;
use Nette\Collections\ArrayList;

use dibi;
use DibiException;
use DibiDataSource;

use Celebrio\PasswordHasher;
use Celebrio\Core\ServiceProvider;
use Celebrio\AccessControl\AlbireoUser;

use KernelModule\UsersModule\UserException;
use KernelModule\UsersModule\UserSearchingException;

use KernelModule\AclModule\AclRole;


/**
 * User Management
 *
 * @author Albireo Solutions
 */

class UserManagement extends Object {

    public static $creation_hooks = array();

    /**
     * Adds User to Database
     * 
     * @throws \KernelModule\UsersModule\UserException
     * @param AlbireoUser $user
     * @param string $password
     * @return bool
     */
    public function addUser(AlbireoUser $user, $password) {
        
        if (is_null($user->getUsername()) ||
                is_null($user->getEmail()) ||
                is_null($user->getLanguage()) ||
                is_null($user->getTimezone())) {
            throw new UserException("something important is null");
        }
        try {
            dibi::query("INSERT INTO [users]([user_name],[first_name],[last_name],[email],[language],[time_zone]) VALUES
                        (%sN,%sN,%sN,%sN,%sN,%sN)",
                    $user->getUsername(),$user->getFirstName(),$user->getLastName(),$user->getEmail(),$user->getLanguage(),$user->getTimezone());
            $user->setId(dibi::getInsertId());
            $this->addPassword($user, $password, true);
            return true;
        } catch (\DibiException $e) {
            throw new UserException("Could not create user", $e->getCode(),$e);
        }
    }

    public function updateUser(AlbireoUser $user, $password = null) {
        try {
            //dibi::test
            dibi::query("UPDATE [users] SET %a WHERE user_name = %s",
                array(
                    'first_name' => $user->getFirstName(),
                    'last_name' => $user->getLastName(),
                    'email' => $user->getEmail(),
                    'language' => $user->getLanguage(),
                    'time_zone' => $user->getTimezone(),
                ),
                $user->getUsername()
            );
            if (!empty($password)) {
                $this->addPassword($user, $password, false);
            }
            return true;
        } catch (\DibiException $e) {
            return false;
        }
    }

    /**
     * Deletes user from DB
     *
     * @param AlbireoUser $user
     * @return bool
     */
    public function deleteUser(AlbireoUser $user) {
        try {
            if ($user->getUsername() == null) {
                throw new \Exception("User with no name given");
            }
            dibi::query("DELETE FROM [users] WHERE [user_name] = %s", $user->getUsername());
            ServiceProvider::getService("Celebrio-Database-IDatabase")->deleteLogin($user->getUsername());
            return true;
        } catch (\DibiException $e) {
            return false;
        }
    }

    /**
     * ADD or UPDATE pass (by setting $newUser = false) - in Albireo
     * you don't update
     *
     * @param AlbireoUser $user
     * @param String $password
     * @param boolean $newUser
     */
    public function addPassword(AlbireoUser $user, $password, $newUser = false) {
        if ($user->getId() == 0 || empty($password)) {
            return false;
        }
        try {
            $pass = PasswordHasher::userHash($user->getUsername(), $password);
            dibi::query("INSERT INTO [passwords]([user_id],[password]) VALUES
                        (%iN,%sN)",$user->getId(),$pass);
            ServiceProvider::getService("Celebrio-Database-IDatabase")->createLogin($user->getUsername(),$pass,$newUser);
        } catch (DibiException $e) {
            return false;
        }
    }

    public function getUsersDataSource() {
        return new DibiDataSource("users", dibi::getConnection());
    }
    
    /**
     * returns user from db by his id
     *
     * @param int $user_id
     * @return AlbireoUser
     */
    public function getUser($user_id) {
        try {
            $userArray = dibi::fetch("SELECT [id],[user_name],[first_name],[last_name],[email],[created],[language],[time_zone]
            FROM [users] WHERE [id] = %i", $user_id);
            if ($userArray) {
		$user = new AlbireoUser($userArray["user_name"],
			$userArray["first_name"],
			$userArray["last_name"],
			$userArray["email"],
			$userArray["created"],
			$userArray["language"],
			$userArray["time_zone"]);
                $user->setId($userArray["id"]);
                return $user;
            } else {
                return null;
            }
        } catch (\DibiException $e) {
            return null;
        }
    }

    /**
     * returns user from db by username
     *
     * @param String $username
     * @return AlbireoUser
     */
    public function getUserByUsername($username) {
        try {
            $userArray = dibi::fetch("SELECT [id],[user_name],[first_name],[last_name],[email],[created],[language],[time_zone]
            FROM [users] WHERE [user_name] = %s", $username);
            if ($userArray) {
                $user = new AlbireoUser($userArray["user_name"],
			$userArray["first_name"],
			$userArray["last_name"],
			$userArray["email"],
			$userArray["created"],
			$userArray["language"],
			$userArray["time_zone"]);
                $user->setId($userArray["id"]);
                return $user;
            } else {
                return null;
            }
        } catch (\DibiException $e) {
            return null;
        }
    }

    /**
     * Connects user in DB with existing Role in DB
     *
     * @param AlbireoUser $user
     * @param AclRole $role
     * @return bool
     */
    public function addUserToRole(AlbireoUser $user,AclRole $role) {
        if ($user->getId() == 0 || $role->getId() == 0) {
            return false;
        }
        try {
            dibi::query("INSERT INTO [users_roles] ([user_id],[role_id]) VALUES (%iN,%iN);",$user->getId(),$role->getId());
        } catch (\DibiException $e) {
            return false;
        }
    }

    /**
     * "Smart search" method. Tries to find any user with the username, first name,
     * last name or e-mail address similar to the $input parameter. When the input
     * contains space, only first name and surname is searched (but in both orders).
     *
     * @param String $input the string we want to "smart search", for example part of the e-mail address
     * @param int $limit The maximum users we want to have in result (0 = unlimited)
     * @return DataSource containing the users who match input, ordered by UserName
     */
    public function getFilteredUsersDataSource($input, $limit = 0) {
        $spaces = strstr($input, " ");
        if ($spaces === FALSE) {
            $input = $input."%";
            $ds = $this->getUsersDataSource()->where("%or", array(
                array ("[user_name] LIKE %s", $input),
                array ("[first_name] LIKE %s", $input),
                array ("[last_name] LIKE %s", $input),
                array ("[email] LIKE %s", $input),
            ));

            //\Nette\Debug::dump($this->getUsersDataSource()->where("[user_name] < %s", $input)->count());
        } else if (substr_count($input, " ") == 1) {
            $names = explode(" ", $input);
            $ds = dibi::getConnection()->dataSource("SELECT * FROM [users] WHERE
                ([first_name] LIKE %s AND [last_name] LIKE %s) OR
                ([first_name] LIKE %s AND [last_name] LIKE %s)",
                $names[0]."%", $names[1]."%", $names[1]."%", $names[0]."%"
            );
        } else {
            throw new UserSearchingException("more than 2 spaces");
        }
        $ds->orderBy("user_name");

        if ($limit != 0) {
            $ds->applyLimit($limit);
        }
        //\Nette\Debug::dump($ds->fetchAll()); //bacha, co se jednou fetchne, uz je pryc
        return $ds;
    }


    public function addRolesforUser($roles, $userId) {
        foreach ($roles as $role) {
            dibi::query('INSERT INTO [users_roles] ([user_id], [role_id]) VALUES (%i, %i);', $userId, $role);
        }
    }

    public function deleteAllRolesforUser($userId) {
        dibi::query('DELETE FROM [users_roles] WHERE [user_id]=%i;', $userId);
    }

    public function getRolesByUserId($userId) {
        $rolesList = new ArrayList();
        try {
            $allRoles = dibi::fetchAll("SELECT * FROM [users_roles] JOIN [roles]
                ON [role_id] = [id]
                WHERE [user_id] = %i", $userId);
        } catch (DibiException $e) {
            return $rolesList;
        }
        foreach($allRoles as $roleRow) {
            $role = new AclRole();
            $role->setId($roleRow["role_id"]);
            $role->setName($roleRow["name"]);
            $role->setParentId($roleRow["parent_id"]);
            $role->setCreated($roleRow["created"]);
            $rolesList->append($role);
        }
        return $rolesList;
    }

    public function getUsersForRole($roleId) {
        $usersList = new ArrayList();
        try {
            $allUsers = dibi::fetchAll("SELECT * FROM [users_roles] JOIN [users]
                ON [user_id] = [id]
                WHERE [role_id] = %i
                ORDER BY user_name", $roleId);
        } catch (DibiException $e) {
            return $usersList;
        }
        foreach($allUsers as $userRow) {
            $user = new AlbireoUser($userRow["user_name"],
		    $userRow["first_name"],
		    $userRow["last_name"],
		    $userRow["email"],
		    $userRow["created"],
		    $userRow["language"],
		    $userRow["time_zone"]);
	    $user->setId($userRow["user_id"]);
            $usersList->append($user);
        }
        return $usersList;
    }

    public function updateRole(AclRole $role) {
        try {
            dibi::query("UPDATE [" . $this->roles . "] SET %a WHERE id = %i",
                array(
                    "name" => $role->getName(),
                    "parent_id" => $role->getParentId(),
                ),
                $role->getId()
            );
            return true;
        } catch (DibiException $e) {
            return false;
        }
    }

    public function getRoleById($id) {
        try {
            $roleArray = dibi::fetch("SELECT [id],[name],[parent_id],[created] FROM [" . $this->roles . "] WHERE [id] = %i", $id);
            if ($roleArray) {
                $role = new AclRole();
                $role->setId($roleArray["id"]);
                $role->setParentId($roleArray["parent_id"]);
                $role->setName($roleArray["name"]);
                $role->setCreated($roleArray["created"]);
                return $role;
            } else {
                return null;
            }
        } catch (DibiException $e) {
            return null;
        }
    }
}
